/*
 * Copyright (C) 2010-2011 Comcast Cable.  All Rights Reserved.
 */
package com.hercules.android.receiver.handlers.events;

import com.hercules.android.receiver.AndroidReceiver;
import com.hercules.android.receiver.ApplicationData;
import com.hercules.android.receiver.LogUtil;
import com.hercules.android.receiver.handlers.commands.XRECommandHandlerException;
import com.hercules.android.receiver.handlers.commands.XREObjectCommandHandler;
import com.hercules.android.receiver.model.commands.XRECallCommand;
import com.hercules.android.receiver.model.commands.XRECommand;
import com.hercules.android.receiver.model.events.XREEvent;
import com.hercules.android.receiver.model.events.XREEventPhase;

import java.util.List;
import java.util.logging.Logger;

/**
 * <p>
 * This class extends XREObjectCommandHandler. It is responsible for handling the XRECallCommand that calls the
 * "generateEvent()" method of XREApplication or XREView. It simply raises the XREEvent from the method call parameter
 * to server with EventRaiser.
 * </p>
 * <p>
 * AndroidAssembly1 some method were removed
 * <p>
 * <strong>Thread safety: </strong> this class is thread-safe because base class is thread-safe after initialization,
 * the EventRaiser it uses is thread-safe, and it's immutable.
 * </p>
 *
 * @author mekanizumu, jaindsonvs
 * @author 7realm
 * @version 1.0
 */
public class XREGenerateEventCallCommandHandler extends XREObjectCommandHandler {
    /**
     * <p>
     * The logger used for logging.
     * </p>
     * <p>
     * Initialization and Mutability: It is final and won't change once it is initialized as part of variable
     * declaration to: Logger.getLogger(XREGenerateEventCallCommandHandler.class.getName()).
     * </p>
     * <p>
     * Usage: It's used in all public methods.
     * </p>
     */
    private static final Logger LOGGER = LogUtil.getLogger(XREGenerateEventCallCommandHandler.class.getSimpleName());
    /**
     * <p>
     * The EventRaiser used to raise XRE events.
     * </p>
     * <p>
     * Initialization and Mutability: It's initialized within constructor, won't change afterwards.
     * </p>
     * <p>
     * Usage: It is used in processCommandMessage(), XREGenerateEventCallCommandHandler() (for initialization).
     * </p>
     */
    private final EventRaiser eventRaiser;

    /**
     * <p>
     * Create an instance of the class.
     * </p>
     *
     * @param eventRaiser the EventRaiser used to raise XRE events.
     * @throws IllegalArgumentException if eventRaiser doesn't conform to the following legal value specification: It
     * cannot be null.
     */
    public XREGenerateEventCallCommandHandler(EventRaiser eventRaiser) {
        EventsUtil.checkArgumentNull(eventRaiser, "eventRaiser");

        this.eventRaiser = eventRaiser;
    }

    /**
     * <p>
     * Process a command message.
     * </p>
     * <p>
     * AndroidAssembly1 this method was changed, exception handling corrected
     *
     * @param command the command to process.
     * @param receiver the receiver that holds the necessary data.
     * @throws IllegalArgumentException if command is null;
     * @throws XRECommandHandlerException if any error occurs while handling the command.
     */
    @Override
    public void processCommandMessage(XRECommand command, AndroidReceiver receiver) throws XRECommandHandlerException {
        final String methodName = "processCommandMessage";
        LogUtil.enter(LOGGER, this, methodName, command, receiver);

        try {
            EventsUtil.checkArgumentNull(command, "command");
            EventsUtil.checkArgumentNull(receiver, "receiver");

            if (!(command instanceof XRECallCommand)) {
                throw new XRECommandHandlerException("The command should be an instance of XRECallCommand.");
            }

            XRECallCommand callCommand = (XRECallCommand) command;

            // AndroidAssembly3 added support of generateAppEvent
            if ("generateEvent".equals(callCommand.getMethod()) || "generateAppEvent".equals(callCommand.getMethod())) {
                // get command parameter
                List<Object> params = callCommand.getParams();
                // AndroidAssembly2 obvious bug, params.size() != 0
                if (params == null || params.size() < 1 || !(params.get(0) instanceof XREEvent)) {
                    throw new XRECommandHandlerException(
                        "XRE call generate event command first parameter should be XREEvent.");
                }
                XREEvent xreEvent = (XREEvent) params.get(0);

                // AndroidAssembly2 set source
                xreEvent.setSource(callCommand.getTargetId());

                // get receiver that will be used to raise event
                AndroidReceiver receiverToUse = receiver;
                if ("generateAppEvent".equals(callCommand.getMethod())) {
                    if (params.size() != 2) {
                        throw new XRECommandHandlerException(
                            "XRE call 'generateAppEvent' command must have two parameters.");
                    }
                    Object guid = params.get(1);
                    if (guid instanceof String) {
                        receiverToUse = getReceiverToUse(receiver, (String) guid);
                        if (receiverToUse == null) {
                        	throw new XRECommandHandlerException("Cannot find receiver for GUID '" + guid + "'.");
                        /*	receiverToUse = receiver;
                        	while (receiverToUse.getApplicationData().getParentReceiver() != null) {
                        		receiverToUse = receiverToUse.getApplicationData().getParentReceiver();
                            }*/
                        }
                    } else {
                        throw new XRECommandHandlerException(
                            "XRE call 'generateAppEvent' command second parameter should be session GUID of String type.");
                    }
                }

                // raise event, exception will be ignored
                boolean result = eventRaiser.raiseEvent(xreEvent, receiverToUse, true);
                if (!result && "generateAppEvent".equals(callCommand.getMethod())) {
                	LogUtil.error(LOGGER, this, methodName, new XRECommandHandlerException(
                			"Failed to generate app event " + xreEvent.getName()));
                }
            }
        } catch (IllegalArgumentException e) {
            throw LogUtil.error(LOGGER, this, methodName, e);
        } catch (XRECommandHandlerException e) {
            throw LogUtil.error(LOGGER, this, methodName, e);
        } catch (EventRaisingException e) {
            throw LogUtil.error(LOGGER, this, methodName, new XRECommandHandlerException(
                "Error occurred while raising event.", e));
        }

        LogUtil.exit(LOGGER, this, methodName);
    }

    /**
     * Returns the proper receiver to be used to raise the event.
     * <p>
     * AndroidAssembly3 fixed some bugs from development version
     *
     * @param receiver the root receiver.
     * @param sessionGUID the session guid
     * @return the proper receiver.
     */
    private static AndroidReceiver getReceiverToUse(AndroidReceiver receiver, String sessionGUID) {
        while (receiver.getApplicationData().getParentReceiver() != null) {
            receiver = receiver.getApplicationData().getParentReceiver();
        }

        return findReceiverBySessionGUID(receiver, sessionGUID);
    }

    /**
     * Find a receiver that matches the given session GUID from the whole hierarchy starting from receiver as the root.
     * <p>
     * AndroidAssembly3 fixed some bugs from development version
     *
     * @param sessionGUID the session GUID to match.
     * @param receiver the root receiver.
     * @return the receiver that matches the given session GUID, or null if not found.
     */
    private static AndroidReceiver findReceiverBySessionGUID(AndroidReceiver receiver, String sessionGUID) {
        // check if session GUID of passed receiver matches
        ApplicationData appData = receiver.getApplicationData();
        if (sessionGUID.equals(appData.getSessionGUID())) {
            return receiver;
        }

        // check all nested receivers
        if (null !=  appData.getNestedXREApplicationViewsIdToReceiver()) {
	        for (AndroidReceiver childReceiver : appData.getNestedXREApplicationViewsIdToReceiver().values()) {
	            AndroidReceiver result = findReceiverBySessionGUID(childReceiver, sessionGUID);
	            if (result != null) {
	                return result;
	            }
	        }
        }

        // no receiver was found
        return null;
    }
}
